import wx
import os, sys, time, locale, threading
from stat import *

import gettext, lib.gettext_win32
gettext.install('Guidu', 'i18n');

InspectingPhrase = _("Inspecting: ");
InspectingPhraseLen = len(InspectingPhrase);

class WalkTree(threading.Thread):
		'''recursively descend the directory tree rooted at top,
			filling directory tree'''
		def __init__(self, form, lock, top, top_item, root = False):
			threading.Thread.__init__(self)
			self.form = form
			self.top = top
			self.tree = form.tree
			self.top_item = top_item
			self.root = root
			self.prev_is_root = True;
			self.lock = lock
			self.current_path = "";
			self.skip_path = "";
			
		def run(self):
			self.WalkTree(self.top, self.top_item, self.root)
			# print "finished run"
			if not self.form.closing:
				self.tree.Refresh()
				self.form.inspect_btn.Enable()
				self.form.inspect_btn.SetLabel("Inspect");
				self.form.inspect_text.Hide();
				self.form.main_panel.Layout();
			self.form.walkthread = None
			if self.form.closing:
				self.form.Close();
			
		def WalkTree(self, top, top_item, root = False):
#			print "i'm alive";
			
			self.current_path = top;
			#print self.skip_path, top[:len(self.skip_path)];
			if self.ShouldSkip(top):
				return -1;
							
			if self.form.stop_inspection:
#				print "exiting due to close 1"
				return -1;
#			print "entered with ", top

			total_size = 0
			item_str = top if root else os.path.split(top)[1]
#			print "getting lock 1"
#			if not self.AcquireLock():
#				print "failed to get lock 1 returning"
#				return -1;
			this_item = self.tree.AppendItem(top_item, item_str)
			if self.prev_is_root and not root:
				self.tree.Expand(top_item);
				self.prev_is_root = False;
			self.tree.SetItemImage(this_item, 0)
			if top_item.IsOk() and self.tree.IsVisible(top_item) and self.tree.GetChildrenCount(top_item) == 1 or self.tree.IsVisible(this_item):
				self.tree.Refresh()
#			self.lock.release();
			files = []
	
#			print "entering cycle"
			try:
				for f in os.listdir(top):					
					if self.ShouldSkip(top):
						return -1;
					pathname = os.path.join(top, f)
					mode = os.stat(pathname)[ST_MODE]
					size = os.stat(pathname)[ST_SIZE] 
					if S_ISDIR(mode):
						size = self.WalkTree(pathname, this_item)
					total_size += size
					files.append([HumanSize(size), pathname, size])
			except WindowsError, (errno, errstr):
				total_size = -1
				#if errno != 5:
				#	raise

#			print "preparing to check self.form";
			if self.form.stop_inspection:
#				print "exiting due to close 2"
				return -1;
		
			total_size_str = HumanSize(total_size)			
			item_data = wx.TreeItemData(files)
#			print "getting lock 2"
#			if not self.AcquireLock():
#				print "failed to get lock 2 returning"
#				return -1;
			self.tree.SetItemData(this_item, item_data)
			self.tree.SetItemText(this_item, '%s %s' % (total_size_str, item_str))
			self.tree.SetItemImage(this_item, 1 if total_size >= 0 else 2)
			if self.tree.IsVisible(this_item):
				self.tree.Refresh()
#			self.lock.release();
			return total_size
			
		def ShouldSkip(self, path):
			if len(self.skip_path):
				if path[:len(self.skip_path)] == self.skip_path:
					return True;
				else:
					self.skip_path = "";
			return False;
								
#		def AcquireLock(self):
#			while not self.lock.acquire(False):
#				if self.form.closing:
#					return False
#				time.sleep(0.1);
#			return True;

class MainFrame(wx.Frame):
	"""
	MainFrame - main frame with a disk tree and various buttons
	"""
	def __init__(self, parent, title):
		wx.Frame.__init__(self, parent, wx.ID_ANY, title,
						  pos=(150, 150), size=(700, 500))

		self.Bind(wx.EVT_CLOSE, self.onClose)

		# Create the menubar
		menuBar = wx.MenuBar()

		# and a menu 
		menu = wx.Menu()

		# add an item to the menu, using \tKeyName automatically
		# creates an accelerator, the third param is some help text
		# that will show up in the statusbar
		menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample")

		# bind the menu event to an event handler
		self.Bind(wx.EVT_MENU, self.onMenuExit, id=wx.ID_EXIT)

		# and put the menu on the menubar
		menuBar.Append(menu, "&File")
		self.SetMenuBar(menuBar)

		self.CreateStatusBar()
		
		tree_ilst = wx.ImageList(16, 16)
		tree_ilst.Add(wx.Bitmap("images/icon_clock.gif", wx.BITMAP_TYPE_GIF))
		tree_ilst.Add(wx.Bitmap("images/icon_accept.gif", wx.BITMAP_TYPE_GIF))
		tree_ilst.Add(wx.Bitmap("images/action_stop.gif", wx.BITMAP_TYPE_GIF))

		# Now create the Panel to put the other controls on.
		#panel = wx.Panel(self)

		# and a few controls
		#text = wx.StaticText(panel, -1, "Hello World!")
		#text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD))
		#text.SetSize(text.GetBestSize())
		#btn = wx.Button(panel, -1, "Close")
		#funbtn = wx.Button(panel, -1, "Just for fun...")

		# bind the button events to handlers
		#self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, btn)
		#self.Bind(wx.EVT_BUTTON, self.OnFunButton, funbtn)
		
		# Use a sizer to layout the controls, stacked vertically and with
		# a 10 pixel border around each
	   # sizerA = wx.BoxSizer(wx.HORIZONTAL)
		#sizerA.Add(self.tree, 0, wx.EXPAND, 10)

		#sizerB = wx.BoxSizer(wx.VERTICAL)
		#sizerB.Add(text, 0, wx.ALL, 10)
		#sizerB.Add(btn, 0, wx.ALL, 10)
		#sizerB.Add(funbtn, 0, wx.ALL, 10)

		splitter = wx.SplitterWindow(self)

		#tree_panel = wx.Panel(splitter)

		tree = wx.TreeCtrl(splitter, wx.ID_ANY, style=wx.NO_BORDER)
		tree.SetMinSize(minSize=(20, 0))
		tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onDirSelected)
		tree.AssignImageList(tree_ilst)
		self.tree = tree

		main_panel = wx.Panel(splitter)
		main_panel.SetMinSize(minSize=(50, 0))
		self.main_panel = main_panel;

		filelist = wx.ListCtrl(main_panel)
		filelist.Bind(wx.EVT_LIST_COL_CLICK, self.onColumnClick)
		filelist.SetMinSize(minSize=(20, 200))
		filelist.SetSingleStyle(wx.LC_REPORT, True)
		filelist.InsertColumn(0, "Size")
		filelist.InsertColumn(1, "Name")
		self.filelist = filelist

		inspect_btn = wx.Button(main_panel, wx.ID_ANY, "Inspect")
		inspect_btn.Bind(wx.EVT_BUTTON, self.onInspect)
		self.inspect_btn = inspect_btn;

		inspect_text = wx.TextCtrl(main_panel, wx.ID_ANY, style=wx.TE_RICH | wx.TE_DONTWRAP | wx.TE_READONLY | wx.ALWAYS_SHOW_SB | wx.NO_BORDER);
		inspect_text.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE));
		inspect_text.Bind(wx.EVT_LEFT_DOWN, self.onInspectLeftClick);
		inspect_text.Bind(wx.EVT_MOTION, self.onInspectMotion);
		inspect_text.Bind(wx.EVT_SCROLLWIN_THUMBTRACK, self.onInspectScroll);
		inspect_text.Bind(wx.EVT_SCROLLWIN_THUMBRELEASE, self.onInspectScrollEnd);		
		color_db = wx.ColourDatabase();		
		normal_attr = wx.TextAttr(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT), wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE));
		normal_attr.SetFont(wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False));
		normal_attr.SetFlags(normal_attr.GetFlags() & ~wx.TEXT_ATTR_TEXT_COLOUR & ~wx.TEXT_ATTR_FONT_UNDERLINE | wx.TEXT_ATTR_BACKGROUND_COLOUR);
		underline_attr = wx.TextAttr(color_db.Find("BLUE"), wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE));
		underline_attr.SetFont(wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, True));
		underline_attr.SetFlags(wx.TEXT_ATTR_TEXT_COLOUR | wx.TEXT_ATTR_FONT_UNDERLINE | wx.TEXT_ATTR_BACKGROUND_COLOUR);
		inspect_text.normal_attr = normal_attr;
		inspect_text.underline_attr = underline_attr;

		"""
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText(InspectingPhrase);
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("C:");
		inspect_text.SetDefaultStyle(normal_attr);
		inspect_text.AppendText("\\");
		inspect_text.SetDefaultStyle(underline_attr);
		inspect_text.AppendText("Temp");
		"""
		
		inspect_text.SetMinSize(minSize=(0, inspect_text.GetBestSize()[1] + 13));
		inspect_text.ast_cursor = None;
		inspect_text.Hide();
		self.inspect_text = inspect_text;

		#inspect_sizer = wx.BoxSizer(wx.VERTICAL);
		#inspect_sizer.Add(inspect_text, 0, wx.EXPAND, 10);
		#inspect_sizer.Add(inspect_lbl, 0, wx.ALL, 10);

		#inspect_lbl3 = wx.StaticText(main_panel, wx.ID_ANY, "InspectingInspectingInspectingInspecting")
		#inspect_lbl4 = wx.StaticText(main_panel, wx.ID_ANY, "InspectingInspectingInspectingInspecting")
		#inspect_lbl5 = wx.StaticText(main_panel, wx.ID_ANY, "InspectingInspectingInspectingInspecting")
		#inspect_lbl6 = wx.StaticText(main_panel, wx.ID_ANY, "InspectingInspectingInspectingInspecting")
		#inspect_sizer.Add(inspect_lbl3, 0, wx.ALL, 10);
		#inspect_sizer.Add(inspect_lbl4, 0, wx.ALL, 10);
		#inspect_sizer.Add(inspect_lbl5, 0, wx.ALL, 10);
		#inspect_sizer.Add(inspect_lbl6, 0, wx.ALL, 10);

		sizer = wx.BoxSizer(wx.VERTICAL)
		sizer.Add(filelist, 0, wx.ALL | wx.EXPAND, 10)
		sizer.Add(inspect_text, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 10)
		sizer.Add(inspect_btn, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)

		main_panel.SetSizer(sizer)
		main_panel.Layout()

		splitter.SplitVertically(tree, main_panel, 200)

		self.current_files = []
		self.walkthread = None
		self.tree_lock = threading.Lock();
		self.stop_inspection = False;
		self.closing = False;
		self.update_inspect_timer = None;
		
	def Inspecting(self):
		return self.walkthread != None;

	def onDirSelected(self, evt):
		item = evt.GetItem()
		files = self.tree.GetItemData(item).GetData()
		self.filelist.DeleteAllItems()
		self.current_files = files
		if files == None:
			return
		for i in range(len(files)):
			item = self.filelist.Append(files[i][:2])
			self.filelist.SetItemData(item, i)
		#for f in files:
		#	self.filelist.Append(f)

	def onMenuExit(self, evt):
		self.Close()

	def onClose(self, evt):
		if self.walkthread != None:
			if wx.MessageDialog(self, "Inspection in progress, cancel it?", "Really quit?", wx.YES | wx.NO | wx.NO_DEFAULT | wx.ICON_QUESTION).ShowModal() == wx.ID_YES:
				self.closing = True
				self.stop_inspection = True;
			return False;
			#print "acquiring lock";
			#while not self.tree_lock.acquire(False):
			#	print "sleeping";
			#	time.sleep(0.1);
			#print "joining"
			#self.walkthread.join()			
		#print "finished"

		if self.update_inspect_timer:
			self.update_inspect_timer.cancel();

		self.Destroy()
		return True

	def onInspect(self, evt):
		self.Inspect() if not self.Inspecting() else self.StopInspection();

	def Inspect(self):
		"""Launch tree walk"""
		dirdlg = wx.DirDialog(self, "Choose disk or folder to inspect", "", wx.DD_DEFAULT_STYLE & ~wx.DD_NEW_DIR_BUTTON | wx.DD_DIR_MUST_EXIST)
		dirdlg.ShowModal()
		path = dirdlg.GetPath()
		if len(path) > 0:
			self.stop_inspection = False;
			#self.inspect_lbl.SetLabel("Inspecting: " + path);
			self.update_inspect_timer = threading.Timer(0.5, self.UpdateInspectLabel);
			self.update_inspect_timer.start();
			self.AssignInspectLabel(path);
			self.inspect_text.Show();
			# self.inspect_btn.Disable()
			self.inspect_btn.SetLabel("Stop");
			self.main_panel.Layout()
			self.tree.DeleteAllItems()
			# self.WalkTree(path, self.tree.GetRootItem(), True)
			self.walkthread = WalkTree(self, self.tree_lock, path, self.tree.GetRootItem(), True)
			self.walkthread.start()
			# self.tree.Refresh()		
			
	def UpdateInspectLabel(self):
		self.update_inspect_timer = None;
		if self.walkthread:
			self.AssignInspectLabel(self.walkthread.current_path);
			self.update_inspect_timer = threading.Timer(0.5, self.UpdateInspectLabel);
			self.update_inspect_timer.start();
			
	def StopInspection(self):
		self.stop_inspection = True;
		self.inspect_btn.Disable();		
			
	def onInspectLeftClick(self, evt):
		"""Handle click on 'Inspecting...' text field"""
		#print self.inspect_text.GetBestSize();
		if self.walkthread:
			hit_test = self.InspectHitTest(evt.GetX(), evt.GetY());
			if len(hit_test):
				self.walkthread.skip_path = hit_test;
		
	def onInspectMotion(self, evt):
		is_hand = True if len(self.InspectHitTest(evt.GetX(), evt.GetY())) > 0 else False;
		if is_hand and self.inspect_text.ast_cursor == None:			
			# improve performance
			self.inspect_text.SetCursor(wx.StockCursor(wx.CURSOR_HAND));
			self.inspect_text.ast_cursor = wx.CURSOR_HAND;
		elif not is_hand and self.inspect_text.ast_cursor != None:
			# improve performance
			self.inspect_text.SetCursor(wx.StockCursor(wx.CURSOR_ARROW));
			self.inspect_text.ast_cursor = None;
			
	def onInspectScroll(self, evt):
		if self.update_inspect_timer != None:
			self.update_inspect_timer.cancel();
			self.update_inspect_timer = None;
		evt.Skip();

	def onInspectScrollEnd(self, evt):
		if self.walkthread != None:
			self.UpdateInspectLabel();
		evt.Skip();

	def onColumnClick(self, evt):
		col_id = evt.GetData()
		if col_id == 0:
			self.filelist.SortItems(self.FilelistSortBySize)

	def InspectHitTest(self, x, y):
		hit_test = self.inspect_text.HitTest((x, y));
		clicked_pos = hit_test[1];
		true_label = self.inspect_text.GetLabel()[InspectingPhraseLen:];
		clicked_pos -= InspectingPhraseLen;
		# check syntax improvement
		# != 0 ... magic??
		if hit_test[0] != 0 or clicked_pos < 0 or clicked_pos == len(true_label)or true_label[clicked_pos:clicked_pos + 1] == "\\" :
			return "";
		# TODO pathsep
		last_slash = true_label.find("\\", clicked_pos);
		return true_label[:last_slash if last_slash >= 0 else len(true_label)]
		
	def AssignInspectLabel(self, label):
		# TODO pathsep 
		scroll_pos = self.inspect_text.GetScrollPos(wx.HORIZONTAL);
		self.inspect_text.Freeze();
		self.inspect_text.ChangeValue("");
		self.inspect_text.SetDefaultStyle(self.inspect_text.normal_attr);
		self.inspect_text.AppendText(InspectingPhrase);
		elems = label.rstrip("\\").split("\\");		
		for el in elems:
			self.inspect_text.SetDefaultStyle(self.inspect_text.underline_attr);
			self.inspect_text.AppendText(el);
			self.inspect_text.SetDefaultStyle(self.inspect_text.normal_attr);
			self.inspect_text.AppendText("\\");
		self.inspect_text.Thaw();
		self.inspect_text.SetScrollPos(wx.HORIZONTAL, scroll_pos);

	def FilelistSortBySize(self, a, b):
		if self.current_files[a][2] < self.current_files[b][2]:
			return -1
		elif self.current_files[a][2] == self.current_files[b][2]:
			return 0
		else:
			return 1		

def HumanSize(size):
	if size >= 1073741824:
		size_str = str(round(size / 1073741824.0, 2)) + " Gb"
	elif size >= 1048576:
		size_str = str(round(size / 1048576.0, 2)) + " Mb"
	elif size == 0:
		size_str = "0 b"
	else:
		size_str = str(round(size / 1024.0, 2)) + " Kb"
	return size_str

class GuiduApp(wx.App):
	def OnInit(self):
		frame = MainFrame(None, "Guidu v1.0")
		self.SetTopWindow(frame)

		frame.Show(True)
		return True

reload(sys)
sys.setdefaultencoding(locale.getdefaultlocale()[1])
inspectpath = "D:\Downloads"		
app = GuiduApp(redirect=False)
app.MainLoop()

